#include <ctime>
#include <iostream>
#include <common/Logger.h>
#include <server/Command.h>
#include "PollServer.h"

CTcpPollServer::CTcpPollServer(CMainServer & mainServer)
    : CThreadPool(1,mainServer.pollServerStackSz_)
    , config_(mainServer.config_)
    , readableFdQue_(mainServer.readableFdQue_)
    , fdSockMap_(mainServer.fdSockMap_)
    , addingFd_(mainServer.addingFd_)
    , request_(mainServer.request_)
{}

CTcpPollServer::~CTcpPollServer(){}

void CTcpPollServer::Init()
{
    ipstr_ = config_.GetString("server.listen.ip");
    port_ = config_.GetString("server.listen.port","9530");
    poll_.SetFdTimeout(config_.GetInt("server.fd.timeout",0));          //seconds
    poll_.SetPollTimeout(config_.GetInt("server.poll.timeout",3000));   //ms
}

void CTcpPollServer::Reconfig()
{
    poll_.SetFdTimeout(config_.GetInt("server.fd.timeout",0));          //seconds
    poll_.SetPollTimeout(config_.GetInt("server.poll.timeout",3000));   //ms
}

void CTcpPollServer::ShowConfig() const
{
    using namespace std;
    cout<<"\nTcpPollServer :\n"
        <<"  ipstr_ = "<<ipstr_<<endl
        <<"  port_ = "<<port_<<endl
        <<"  fdtimeout_ = "<<poll_.GetFdTimeout()<<endl
        <<"  polltimeout_ = "<<poll_.GetPollTimeout()<<endl;
}

int CTcpPollServer::doIt()
{
    LOCAL_LOGGER(logger,"CTcpPollServer::doIt");
    prepare();
    __SockPtr client(__CmdSock::GetObject());
    for(;;addPollFd()){
        if(!poll_.Wait()){
            WARN("poll() error "<<CSocket::ErrMsg());
            continue;
        }
        if(poll_[0].Readable()){    //new client
            for(;listen_.Accept(*client);client = __CmdSock::GetObject()){
                if(client->IsValid()){
                    DEBUG("new client arrived "<<client->ToString());
                    ++request_;
                    client->SetLinger();
                    client->SetBlock(false);
                    fdSockMap_.SetSock(client->FD(),client);
                    readableFdQue_.Push(client->FD());
                }
            }
        }
        U32 curtime = time(0);
        for(size_t i = 1;i < poll_.Size();){
            if(poll_[i].Invalid()){
                poll_.RemoveFd(i);
            }else if(poll_[i].Error()){
                __SockPtr tmp = fdSockMap_.GetSock(poll_[i].FD());
                WARN("poll_["<<i<<"].fd="<<poll_[i].FD()<<",sock="<<tmp
                    <<" error, remove it from fdSockMap_");
                fdSockMap_.SetSock(poll_[i].FD(),0);
                poll_.RemoveFd(i);
            }else if(poll_[i].Readable()){
                DEBUG("poll_["<<i<<"].fd="<<poll_[i].FD()<<" can read");
                ++request_;
                readableFdQue_.Push(poll_[i].FD());
                poll_.RemoveFd(i);
            }else if(poll_[i].IsExpired(curtime)){
                __SockPtr tmp = fdSockMap_.GetSock(poll_[i].FD());
                WARN("timeout for poll_["<<i<<"]="<<tmp->ToString()<<", expireTime="
                    <<Tools::TimeString(poll_[i].ExpireTime())<<", remove it from fdSockMap_");
                fdSockMap_.SetSock(poll_[i].FD(),0);
                poll_.RemoveFd(i);
            }else
                ++i;
        }
    }
    return 0;
}

void CTcpPollServer::prepare()
{
    LOCAL_LOGGER(logger,"CTcpPollServer::prepare");
    INFO("MAX fd size="<<Tools::GetMaxFileDescriptor());
    CSockAddr serverAddr;
    if(!serverAddr.SetAddr(ipstr_,port_)){
        FATAL_COUT("server ip="<<ipstr_<<",port_="<<port_<<" error"<<CSockAddr::ErrMsg());
    }
    INFO("listen server addr="<<serverAddr.ToString());
    if(!listen_.Listen(serverAddr,false)){
        FATAL_COUT("listen server addr="<<serverAddr.ToString()<<" error"<<CSocket::ErrMsg());
    }
    poll_.AddFd(listen_.FD(),POLLRDNORM,0);
}

void CTcpPollServer::addPollFd()
{
    size_t oldsz = poll_.Size();
    U32 curtime = time(0);
    CMutexGuard g(addingFd_.Mutex());
    poll_.Reserve(oldsz + addingFd_.size());
    for(__FdQue::iterator it = addingFd_.begin();it != addingFd_.end();++it)
        if(*it > 0)
            poll_.AddFd(*it,POLLRDNORM,curtime);
    addingFd_.clear();
}
